package gdma.hqdes.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import common.mybatis.Queries;
import common.util.TreeUtils;
import gdma.hqdes.bean.*;
import gdma.hqdes.dao.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

import static gdma.hqdes.Constants.ROLE_AUDITOR;
import static gdma.hqdes.Constants.ROLE_REPORTER;
import static gdma.hqdes.bean.UnitScore.*;

/**
 * @Author wangyue
 * @Version 3.0
 * @Date 2024-07-13
 */

@Service
public class UnitServiceImpl implements UnitService {

    @Autowired
    HqdIndexExtMapper indexExtMapper;

    @Autowired
    UnitMapper unitMapper;

    @Autowired
    UnitMemberMapper unitMemberMapper;

    @Autowired
    UnitScoreMapper unitScoreMapper;

    @Autowired
    UnitScoreUpdateMapper unitScoreUpdateMapper;

    @Autowired
    UnitScoreArgMapper unitScoreArgMapper;

    @Autowired
    UnitScoreAttMapper unitScoreAttMapper;

    Map<Integer, Map<String, List<Unit>>> areaUnitsMap = new HashMap<>();

    @PostConstruct
    public void initAreaUnitsMap() {
        for(Integer scope : new Integer[] {Unit.SCOPE_MUNICIPAL, Unit.SCOPE_COUNTY}) {
            Map<String, List<Unit>> subMap = getUnits(scope).stream()
                    .filter(unit -> StringUtils.hasText(unit.getAreaTag()))
                    .collect(Collectors.groupingBy(Unit::getAreaTag));
            areaUnitsMap.put(scope, subMap);
        }
    }

    @Override
    public Unit getUnit(long unitId) {
        return unitMapper.selectById(unitId);
    }

    @Override
    public Unit getUnitByShortName(String shortName) {
        LambdaQueryWrapper<Unit> criteria = new QueryWrapper<Unit>().lambda();
        criteria.eq(Unit::getShortName, shortName);
        return unitMapper.selectOne(criteria);
    }

    @Override
    public Unit getUnitByShortPrefix(String shortPrefix) {
        LambdaQueryWrapper<Unit> criteria = new QueryWrapper<Unit>().lambda();
        criteria.eq(Unit::getShortPrefix, shortPrefix);
        return unitMapper.selectOne(criteria);
    }

    @Override
    public List<Unit> getChildUnits(long parentUnitId) {
        return unitMapper.getChildUnits(parentUnitId);
    }

    @Override
    public List<Unit> getAuthorizedUnits(long userId) {
        Long unitId = unitMemberMapper.selectUnitIdByUserId(userId);
        List<Unit> units = unitMapper.selectList(null);
        if(unitId != null && unitId > 0) {
            List<Unit> trees = TreeUtils.buildTree(units, Unit::getId, Unit::getParentId);
            Unit root = null;
            for(Unit tree : trees) {
                root = TreeUtils.find(tree, node -> unitId.equals(node.getId()));
                if(root != null) break;
            }
            if(root != null) {
                units.clear();
                TreeUtils.traverse(root, node -> units.add(node));
            }
        }
        return units;
    }

    @Override
    public List<Long> getAuthorizedUnitIds(long userId) {
        return getAuthorizedUnits(userId).stream().map(Unit::getId).collect(Collectors.toList());
    }

    @Override
    public List<Unit> getUnits(int scope) {
        LambdaQueryWrapper<Unit> criteria = new QueryWrapper<Unit>().lambda();
        criteria.eq(Unit::getScope, scope);
        return unitMapper.selectList(criteria);
    }

    @Override
    public Page<Unit> listUnits(@Nullable Long id, @Nullable Collection<Long> ids, @Nullable Long parentId, @Nullable String name, @Nullable Integer scope, @Nullable Integer scope0, @Nullable String areaTag, int... pagination) {
        LambdaQueryWrapper<Unit> criteria = new QueryWrapper<Unit>().lambda();
        if(id != null) criteria.eq(Unit::getId, id);
        if(ids != null && !ids.isEmpty()) criteria.in(Unit::getId, ids);
        if(parentId != null) criteria.eq(Unit::getParentId, parentId);
        if(StringUtils.hasText(name)) criteria.like(Unit::getName, name);
        if(scope != null) criteria.eq(Unit::getScope, scope);
        if(scope0 != null)  criteria.ge(Unit::getScope, scope0);
        if(StringUtils.hasText(areaTag)) criteria.eq(Unit::getAreaTag, areaTag);
        return Queries.paginate(() -> unitMapper.selectList(criteria), pagination);
    }

    @Override
    public Map<String, List<Unit>> getAreaUnitsMap(int scope) {
        return areaUnitsMap.get(scope);
    }

    @Override
    public void saveUnit(Unit unit) {
        if(unit.getId() != null) {
            unit.setUpdatedTime(new Date());
            unitMapper.updateById(unit);
        } else {
            unit.setCreatedTime(new Date());
            unitMapper.insert(unit);
        }
    }

    @Override
    public UnitScore getScore(long projectId, long unitId, long indexId) {
        LambdaQueryWrapper<UnitScore> query = new QueryWrapper<UnitScore>().lambda();
        query.eq(UnitScore::getProjectId, projectId);
        query.eq(UnitScore::getUnitId, unitId);
        query.eq(UnitScore::getIndexId, indexId);
        return unitScoreMapper.selectOne(query);
    }

    @Override
    public void saveScore(UnitScore score) {
        score.setUpdatedTime(new Date());
        if(score.getId() != null)
            unitScoreMapper.updateById(score);
        else
            unitScoreMapper.insert(score);
    }

    @Override
    public List<UnitScoreUpdate> getScoreUpdates(long scoreId) {
        LambdaQueryWrapper<UnitScoreUpdate> query = new QueryWrapper<UnitScoreUpdate>().lambda();
        query.eq(UnitScoreUpdate::getScoreId, scoreId);
        query.orderByDesc(UnitScoreUpdate::getUpdatedTime);
        return unitScoreUpdateMapper.selectList(query);
    }

    @Override
    public void logScoreUpdate(UnitScoreUpdate scoreUpdate) {
        unitScoreUpdateMapper.insert(scoreUpdate);
    }

    @Override
    public void saveScoreArg(UnitScoreArg scoreArg) {
        if(scoreArg.getId() != null)
            unitScoreArgMapper.updateById(scoreArg);
        else
            unitScoreArgMapper.insert(scoreArg);
    }

    @Override
    public void deleteScoreArg(Long id) {
        unitScoreArgMapper.deleteById(id);
    }

    @Override
    public void saveScoreAtt(UnitScoreAtt scoreAtt) {
        unitScoreAttMapper.insert(scoreAtt);
    }

    @Override
    public void deleteScoreAtt(Long id) {
        unitScoreAttMapper.deleteById(id);
    }

    @Override
    public List<UnitScore> getThinUnitScores(long projectId) {
        return unitScoreMapper.selectScores(projectId);
    }

    @Override
    public List<UnitScore> getUnitScores(long projectId, long unitId) {
        List<UnitScore> unitScores = unitScoreMapper.selectUnitScores(projectId, unitId);
        Map<Long, List<UnitScoreArg>> unitScoreArgMap = unitScoreArgMapper.selectUnitScoreArgs(projectId, unitId).stream()
                .collect(Collectors.groupingBy(UnitScoreArg::getScoreId));
        Map<Long, List<UnitScoreAtt>> unitScoreAttMap = unitScoreAttMapper.selectUnitScoreAtts(projectId, unitId).stream()
                .collect(Collectors.groupingBy(UnitScoreAtt::getScoreId));
        for(UnitScore unitScore : unitScores) {
            unitScore.setScoreArgs(unitScoreArgMap.getOrDefault(unitScore.getId(), new ArrayList<>()));
            unitScore.setScoreAtts(unitScoreAttMap.getOrDefault(unitScore.getId(), new ArrayList<>()));
        }
        return unitScores;
    }

    @Override
    public UnitMember getMember(long userId) {
        return unitMemberMapper.selectById(userId);
    }

    @Override
    public List<UnitMember> listMembers(long unitId) {
        return unitMemberMapper.selectByUnitId(unitId);
    }

    @Override
    public List<UnitStat> statUnitScores(HqdProject project, Collection<Long> unitIds, Collection<Long> indexIds) {
        if(CollectionUtils.isEmpty(unitIds) || CollectionUtils.isEmpty(indexIds)) return new ArrayList<>();
        return unitMapper.statUnitScores(project.getScope(), project.getId(), unitIds, indexIds);
    }

    @Override
    public List<YearStat> statHistoryUnitScores(Collection<Long> projectIds, Collection<Long> unitIds, Collection<Long> indexIds) {
        if(CollectionUtils.isEmpty(projectIds) || CollectionUtils.isEmpty(unitIds) || CollectionUtils.isEmpty(indexIds)) return new ArrayList<>();
        return unitMapper.statHistoryUnitScores(projectIds, unitIds, indexIds);
    }

    @Override
    public List<UnitStat2> notifyUnitScoresToReport(HqdProject project, Collection<Long> unitIds, Collection<String> roles) {
        if(!roles.contains(ROLE_REPORTER)) return new ArrayList<>();
        List<HqdIndexExt> exts = indexExtMapper.selectByIndexVerId(project.getIndexVerId());
        Map<Long, List<HqdIndexExt>> extMap = exts.stream().collect(Collectors.groupingBy(HqdIndexExt::getIndexId));
        Map<Long, Unit> unitMap = unitMapper.selectBatchIds(unitIds).stream()
                .filter(u -> u.getScope() == project.getScope())
                .collect(Collectors.toMap(Unit::getId, u -> u));
        Map<Long, Map<Long, UnitScore>> unitScoreMap = unitMap.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> {
                    Map<Long, UnitScore> map = new HashMap<>();
                    for(HqdIndexExt ext : exts)
                        map.put(ext.getIndexId(), null);
                    return map;
                }));
        List<UnitScore> unitScores = unitScoreMapper.selectScores(project.getId());
        for(UnitScore unitScore : unitScores) {
            Map<Long, UnitScore> m = unitScoreMap.get(unitScore.getUnitId());
            if(m == null) continue;
            if(unitScore.getStatus() >= UnitScore.STATUS_NOT_AUDITED)
                m.remove(unitScore.getIndexId());
            else
                m.put(unitScore.getIndexId(), unitScore);
        }
        List<UnitStat2> result = unitScoreMap.entrySet().stream()
                .map(e -> {
                    UnitStat2 us = new UnitStat2();
                    Unit unit = unitMap.get(e.getKey());
                    BeanUtils.copyProperties(unit, us);
                    //有空值时，不能用Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)
                    for(Iterator<Long> it = e.getValue().keySet().iterator(); it.hasNext(); ) {
                        Long indexId = it.next();
                        List<HqdIndexExt> indexExtList = extMap.get(indexId);
                        if(indexExtList != null) {
                            Optional<HqdIndexExt> found = indexExtList.stream()
                                    .filter(ext -> ext.getAreaTag() == null || ext.getAreaTag().equals(unit.getAreaTag()))
                                    .findFirst();
                            if(found.isPresent()) {
                                HqdIndexExt indexExt = found.get();
                                if(StringUtils.hasText(indexExt.getReporterRole()) && roles.contains(indexExt.getReporterRole()))
                                    continue;
                                if(StringUtils.hasText(indexExt.getReporterRole2()) && roles.contains(indexExt.getReporterRole2()))
                                    continue;
                            }
                        }
                        it.remove();
                    }
                    us.setScoreMap(e.getValue());
                    return us;
                }).filter(us2 -> !CollectionUtils.isEmpty(us2.getScoreMap())).collect(Collectors.toList());
        return result;
    }

    @Override
    public List<UnitStat2> notifyUnitScoresToAudit(HqdProject project, Collection<Long> unitIds, Collection<String> roles) {
        if(!roles.contains(ROLE_AUDITOR)) return new ArrayList<>();
        List<HqdIndexExt> exts = indexExtMapper.selectByIndexVerId(project.getIndexVerId());
        Map<Long, List<HqdIndexExt>> extMap = exts.stream().collect(Collectors.groupingBy(HqdIndexExt::getIndexId));
        Map<Long, Unit> unitMap = unitMapper.selectBatchIds(unitIds).stream()
                .filter(u -> u.getScope() == project.getScope())
                .collect(Collectors.toMap(Unit::getId, u -> u));
        List<UnitScore> unitScores = unitScoreMapper.selectScores(project.getId());
        Map<Long, Map<Long, UnitScore>> unitScoreMap = unitScores.stream()
                .filter(us -> unitMap.containsKey(us.getUnitId()))
                .collect(Collectors.groupingBy(UnitScore::getUnitId, Collectors.toMap(UnitScore::getIndexId, us -> us)));
        List<UnitStat2> result = unitScoreMap.entrySet().stream()
                .map(e -> {
                    UnitStat2 us = new UnitStat2();
                    Unit unit = unitMap.get(e.getKey());
                    BeanUtils.copyProperties(unit, us);
                    Map<Long, UnitScore> scores = e.getValue().entrySet().stream()
                            .filter(e1 -> {
                                List<HqdIndexExt> indexExtList = extMap.get(e1.getKey());
                                if(indexExtList == null) return false;
                                Optional<HqdIndexExt> found = indexExtList.stream()
                                        .filter(ext -> ext.getAreaTag() == null || ext.getAreaTag().equals(unit.getAreaTag()))
                                        .findFirst();
                                if(found.isPresent()) {
                                    HqdIndexExt indexExt = found.get();
                                    String[] auditorRoles = indexExt.getAuditorRoles();
                                    UnitScore score = e1.getValue();
                                    if(score.getStatus() == STATUS_NOT_AUDITED && roles.contains(auditorRoles[0])) return true;
                                    if(auditorRoles.length == 3 && score.getStatus() == STATUS_APPROVED3 && roles.contains(auditorRoles[1])) return true;
                                    if(auditorRoles.length == 3 && score.getStatus() == STATUS_APPROVED2 && roles.contains(auditorRoles[2])) return true;
                                    if(auditorRoles.length == 2 && score.getStatus()== STATUS_APPROVED2 && roles.contains(auditorRoles[1])) return true;
                                }
                                return false;
                            }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
                    us.setScoreMap(scores);
                    return us;
                }).filter(us2 -> !CollectionUtils.isEmpty(us2.getScoreMap())).collect(Collectors.toList());
        return result;
    }

    @Override
    public List<Unit> listRecentUpdatedUnits(HqdProject project, @Nullable Collection<Long> unitIds, @Nullable String name, @Nullable String areaTag, @Nullable Collection<Long> notifyingUnitIds) {
        return unitMapper.listRecentUpdatedUnits(project.getId(), project.getScope(), project.getIndexVerId(), unitIds, name, areaTag, notifyingUnitIds);
    }

    @Override
    @Transactional
    public int purgeMember(long userId) {
        int n = unitMemberMapper.deleteUserRole(userId);
        n += unitMemberMapper.deleteAccount(userId);
        n += unitMemberMapper.deleteUser(userId);
        return n;
    }

    @Override
    @Transactional
    public int purgeUnit(long unitId) {
        int n = 0;
        List<Long> userIds = unitMemberMapper.selectByUnitId(unitId).stream()
                .map(UnitMember::getId)
                .collect(Collectors.toList());
        if(!CollectionUtils.isEmpty(userIds)) {
            for(long userId : userIds) {
                n = unitMemberMapper.deleteUserRole(userId);
                n += unitMemberMapper.deleteAccount(userId);
                n += unitMemberMapper.deleteUser(userId);
            }
        }
        List<Long> scoreIds = unitScoreMapper.selectOwnedScores(unitId).stream()
                .map(UnitScore::getId)
                .collect(Collectors.toList());
        if(!CollectionUtils.isEmpty(scoreIds)) {
            for(long scoreId : scoreIds) {
                n += unitScoreArgMapper.deleteByScoreId(scoreId);
                n += unitScoreAttMapper.deleteByScoreId(scoreId);
                n += unitScoreUpdateMapper.deleteByScoreId(scoreId);
            }
            n += unitScoreMapper.deleteBatchIds(scoreIds);
        }
        n += unitMapper.deleteById(unitId);
        return n;
    }

}
